package com.zs.usc.security;

import com.zs.usc.common.BaseAuthController;
import com.zs.usc.common.BusinessException;
import com.zs.usc.common.RestCode;
import com.zs.usc.common.RestRst;
import com.zs.usc.constant.Global;
import com.zs.usc.entity.MenuInfo;
import com.zs.usc.entity.RoleInfo;
import com.zs.usc.service.IMenuService;
import com.zs.usc.service.IRoleService;
import com.zs.usc.utils.JsonUtils;
import com.zs.usc.utils.RedisUtils;
import com.zs.usc.utils.SpringUtils;
import com.zs.usc.vo.CurrentUserInfo;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Zero
 * @description 权限切面拦截
 * @date 2021/11/13 16:49
 */
@Component
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE) // 优先级最低
public class AuthorizationAspectInterceptor {


    @Autowired
    private RedisUtils redisUtils;

    @Value("${ssoAuth.serverName:Auth}")
    private String serverName;

    @Autowired
    private IMenuService menuService;


    @Autowired
    private IRoleService roleService;

    // 定义切点
    @Pointcut("@annotation(com.zs.usc.security.Authorization)")
    public void pointCut() {

    }

    // 1.验证登录，同时将登录用户信息放入当前线程环境中 2.判断权限标识
    @Around("pointCut()")
    public Object doLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取判读方法是否存在权限注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Authorization authorization = methodSignature.getMethod().getAnnotation(Authorization.class);
        // 资源
        String menuFlag = authorization.value();
        // 角色
        String[] roleLst = authorization.role();
        // 去除""
        if (roleLst != null && roleLst.length > 0 && StringUtils.isEmpty(roleLst[0])) {
            roleLst = null;
        }
        // 获取前端token
        String token = SpringUtils.getHttpServletRequest().getHeader(Global.AUTHORIZE_TOKEN);
        // 1.未登录 2.登录已过期 3.正常
        if (StringUtils.isEmpty(token))
            return RestRst.build(RestCode.ERROR_ACCOUNT_NOT_LOGIN);

        // 从redis获取登录用户信息 需要重新登录
        if (!redisUtils.hasKey(token))
            return RestRst.build(RestCode.ERROR_ACCOUNT_NOT_LOGIN);
        // 将登录信息传递给方法使用
        String objStr = redisUtils.getStr(token);
        if (StringUtils.isEmpty(objStr))
            return RestRst.build(RestCode.ERROR_ACCOUNT_NOT_LOGIN);

        CurrentUserInfo loginInfo = JsonUtils.jsonToPojo(objStr, CurrentUserInfo.class);
        BaseAuthController.set(loginInfo);

        // 当只有菜单标识无角色控制时
        if (StringUtils.isNotEmpty(menuFlag) && roleLst == null) {
            // 没有访问权限
            if (!hasMenuPerms(loginInfo.getUid(), menuFlag, token)) {
                throw new BusinessException(RestCode.ERROR_NO_ACCESS_FORBIDDEN);
            }
        }
        // 当只有角色无菜单控制时
        if ((roleLst != null && roleLst.length > 0) && StringUtils.isEmpty(menuFlag)) {
            if (!hasRolePerms(loginInfo.getUid(), roleLst, token)) {
                throw new BusinessException(RestCode.ERROR_NO_ACCESS_FORBIDDEN);
            }
        }
        //当菜单角色都有时  满足一个则通过
        if ((roleLst != null && roleLst.length > 0) && StringUtils.isNotEmpty(menuFlag)) {
            if (!hasMenuPerms(loginInfo.getUid(), menuFlag, token) && !hasRolePerms(loginInfo.getUid(), roleLst, token))
                throw new BusinessException(RestCode.ERROR_NO_ACCESS_FORBIDDEN);
        }
        System.out.println("AuthorizationInterceptor passed!");
        return joinPoint.proceed();
    }

    private boolean hasRolePerms(String uid, String[] roleLst, String token) {
        if (roleLst == null || roleLst.length == 0)
            return true;
        String serverNameUp = this.serverName.toUpperCase();
        String roleKey = serverNameUp + ":ROLE:" + uid;
        boolean hasRolePerms = false;

        // 获取缓存中的角色
        Set<String> roleSet = redisUtils.sStringGet(roleKey);
        // 补充数据
        if (CollectionUtils.isEmpty(roleSet)) {

            roleSet = roleService.listRoleByUserId(uid, serverNameUp).stream().map(RoleInfo::getRoleFlag)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toSet());

            // 该用户没有这个应用的角色
            if (CollectionUtils.isEmpty(roleSet))
                return false;
            // 添加缓存
            redisUtils.sStringSetAndTime(roleKey, Global.TOKEN_EXPIRE, roleSet.toArray(new String[roleSet.size()]));
        } else {
            redisUtils.expire(roleKey, Global.TOKEN_EXPIRE);
        }


        // 判断权限
        if (CollectionUtils.isEmpty(roleSet))
            return false;

        for (String role : roleLst) {
            if (roleSet.contains(role)) {
                hasRolePerms = true;
                break;
            }
        }
        return hasRolePerms;
    }

    /**
     * 资源权限判读
     *
     * @param uid      用户id
     * @param menuFlag 该接口访问资源权限
     * @return true:有权限 false:无权限
     */
    private boolean hasMenuPerms(String uid, String menuFlag, String token) {
        //当传入的资源权限信息为空，默认角色验证通过
        if (StringUtils.isEmpty(menuFlag)) {
            return true;
        }
        boolean hasMenu = false;
        String serverNameUp = this.serverName.toUpperCase();
        // 注意这个 相同账户多个地点登录:菜单不需要每次更新--当更新权限时需要重新刷新
        String menuKey = serverNameUp + ":MENU:" + uid;
        Set<String> menuSet = redisUtils.sStringGet(menuKey);
        // 缓存已过期 从鉴权中心获取角色信息
        if (CollectionUtils.isEmpty(menuSet)) {
            try {
                // 放入缓存
                menuSet = menuService.listMenuByUserId(uid, serverNameUp)
                        .stream()
                        .map(MenuInfo::getMenuFlag)
                        .filter(StringUtils::isNotEmpty)
                        .collect(Collectors.toSet());
                if (CollectionUtils.isEmpty(menuSet))
                    return false;
                // 添加缓存
                redisUtils.sStringSetAndTime(menuKey, Global.TOKEN_EXPIRE, menuSet.toArray(new String[menuSet.size()]));

            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        } else {
            // 刷新当前用户权限过期时间
            redisUtils.expire(menuKey, Global.TOKEN_EXPIRE);
        }

        // 权限判读
        if (menuSet.contains(menuFlag))
            hasMenu = true;

        return hasMenu;
    }

//
//    /**
//     * 资源权限判读
//     *
//     * @param uid      用户id
//     * @param menuFlag 该接口访问资源权限
//     * @return true:有权限 false:无权限
//     */
//    private boolean hasMenu(String uid, String menuFlag, String token) {
//        //当传入的资源权限信息为空，默认角色验证通过
//        if (StringUtils.isEmpty(menuFlag)) {
//            return true;
//        }
//        boolean hasMenu = false;
//        String serverNameUp = this.serverName.toUpperCase();
//        // 注意这个 相同账户多个地点登录:菜单不需要每次更新--当更新权限时需要重新刷新
//        String menuKey = serverNameUp + ":MENU:" + uid;
//        Set<String> menuSet = redisUtils.sStringGet(menuKey);
//        // 缓存已过期 从鉴权中心获取角色信息
//        if (CollectionUtils.isEmpty(menuSet)) {
//            try {
//                // 获取用户菜单权限
//                List<MenuInfo> result = menuService.listMenuByUserId(uid, serverNameUp);
////                UriComponentsBuilder builder = UriComponentsBuilder
////                                .fromHttpUrl("http://localhost:9011/usc/menu/getUserMenuList/" + URLEncoder.encode(uid, "utf-8"));
////                URI uri = builder.build(true).toUri();
////
////                HttpHeaders header = new HttpHeaders();
////                header.add(Global.AUTHORIZE_TOKEN, token);
////                RestRst<List<MenuInfo>> qryMenuRst =
////                        restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<String>(header),
////                                new ParameterizedTypeReference<RestRst<List<MenuInfo>>>() {
////                                }).getBody();
//                // 放入缓存
//                if (qryMenuRst.getStatus() == 0) {
//
//                    List<MenuInfo> menuLst = qryMenuRst.getData();
//                    menuSet = menuLst.stream()
//                            .map(MenuInfo::getMenuFlag)
//                            .filter(StringUtils::isNotEmpty)
//                            .collect(Collectors.toSet());
//
//                    if (CollectionUtils.isEmpty(menuSet))
//                        return false;
//                    // 添加缓存
//                    redisUtils.sStringSetAndTime(menuKey, Global.TOKEN_EXPIRE, menuSet.toArray(new String[menuSet.size()]));
//                } else {
//                    return false;
//                }
//            } catch (UnsupportedEncodingException e) {
//                e.printStackTrace();
//                return false;
//            }
//        } else {
//            // 刷新当前用户权限过期时间
//            redisUtils.expire(menuKey, Global.TOKEN_EXPIRE);
//        }
//
//        // 权限判读
//        if (menuSet.contains(menuFlag))
//            hasMenu = true;
//
//        return hasMenu;
//    }
}
